Skip to main content

Statements

Code Block

Statements could be grouped in paired curly brackets. Local variables defined in a code block are not visible outside the block.

{
int32 i;
}
// i no longer defined

if-, for- and some other statements are always followed by a code block as part of the statement. The body of a function definition can also be regarded as a code block.

Variable Declaration Statement

PREDA is a statically-typed language and all variables must be assigned a static type at definition.

MyType myVariable = initializer;

The initializer is optional but its type must match the type used if it's present.

'auto' Keyword

Another way to specify the type is to use the 'auto' keyword. In this case, an initializer must be provided.

auto x = 1u16;   // x is defined as uint16
auto y = "123"; // y is defined as string
auto z; // compile error

The scope of the defined variable is the innermost block that contains it. A variable cannot shadow another one with the same name defined in an outer scope. Instead, it will generate a compile error.

{
int32 i;
}
int32 i; // the i defined above is no longer available here. Hence a new definition of i is possible.
int32 j;
{
int32 j; // re-defining j here will not shadow the definition in the outer scope. Instead, it gets a compile error
}

'const' Keyword

A variable can also be declared as a constant using the 'const' keyword. The data of a constant variable cannot be changed once it's initialized from the initializer in its declaration statement.

const int32 i = 3;   // declaring i as constant
i = 4; // Compile error: a constant value cannot be modified after initialization

Constant variables of reference types behave a bit differently with the assignment operator '=', because it shares the underlying data instead of making a copy. Therefore, for reference types, assigning a constant variable to a non-constant variable would generate a compile error. Otherwise the shared data would be modifiable through the non-constant variable.

struct S{
bool a;
}
const S s0;
S s1;
s1 = s0; // Compile error: Cannot assign a constant reference type to non-constant.

if Statement

if statement has the following syntax

if (condition) {
// statements when condition is satisfied
}
else {
// statements when condition is not satisfied
}

if and else must always be followed by a block, even if there's only one statement in it. The only exception is when else is immediately followed by an if, so they can be chained together like:

if (...) {
}
else if (...) {
}
else if (...) {
}
else {
}

for, do-while and while Statements

for, do-while and while statements has the following syntax

for (init-statement; condition; iteration-expression){
// loop body
}
do{
// loop body, executed at least once
} while (condition);
while (condition){
// loop body
}

for, do-while and while must always be followed by a block, even if there's only one statement in it.

continue and break Statements

continue statement is used to skip the rest of loop body in for, do-while or while statements for the current loop. break statement is used to terminate the corresponding loop statement.

return Statement

return statement is used to end the execution in current function and return to the caller. If the current function has a return type, it must be followed by an expression of the same type.

relay Statement

A relay statement is similar to a function call, except that the call is asynchronous. The call data is packaged in a so-called "relay transaction" and relayed to the target for execution. The relay statement itself returns immediately.

relay@TargetExpression functionName(params);
relay@shards functionName(params);
relay@global functionName(params);

There are 3 types of relay targets, as shown in the above example. The first type is the general form, where TargetExpression is an expression that evaluates to a type that matches the function's scope.

The second type is a broadcast relay, which uses the 'shards' keyword. It relays to all the non-global shards, like a broadcast. In this case, the called function must be defined in the shard scope.

The last type is a global relay, which uses the 'global' keyword. It relays to the global shard and must be called from a shard- or address- function. The function must be defined in the global scope.

In all types, the function being called must be from the same contract.

relay Statement with Lambda Function

Alternatively, relay statement define a lambda function inline and relay to it.

relay@TargetExpression|'shards'|'global' (['const'] parameterType parameterName = argumentExpression, ...) ['const']{
// function body
}

The format is quite similar to defining a function except that:

  1. A function name is not needed. The compiler automatically generates a name for it.
  2. The scope of the anonymous function is TargetExpression, shard or global, based on the relay type.
  3. For each parameter, an argument must be provided as well.
  4. It is possible to use the 'auto' keyword as parameter type. In this case, the type is taken from the corresponding argument expression.

Be aware that the relay function body is executed on the per-address context of TargetAddress, the per-shard context of the target shards, or the global context. It is not to be mixed with the current context on which the relay statement is invoked.

To simplify the code, there's another way to specify a parameter in the relay lambda:

relay@someAddress (..., ^identifier, ...){
}

This is exactly the same as

relay@someAddress (..., auto identifier = identifier, ...){
}

deploy Statement

The deploy statement is used to programmatically create a new contract on chain from within a contract.

deploy contractName(parameters);

For more details check Deploy Unnamed Contract.